package com.xiaobange.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaobange.constants.RedisKey;
import com.xiaobange.dto.ReservationListPage;
import com.xiaobange.entity.Reservation;
import com.xiaobange.exception.ServiceException;
import com.xiaobange.mapper.ReservationMapper;
import com.xiaobange.service.ReservationService;
import com.xiaobange.utils.RedisCache;
import com.xiaobange.utils.ResponseCodeEnum;
import com.xiaobange.vo.RequestPage;
import com.xiaobange.vo.ResultResponse;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author banji
 * @description 针对表【appointment(预约表)】的数据库操作Service实现
 * @createDate 2025-04-24 17:57:21
 */
@Service
@Slf4j
public class ReservationServiceImpl extends ServiceImpl<ReservationMapper, Reservation>
        implements ReservationService {

    @Autowired
    private ReservationMapper reservationMapper;

    @Autowired
    private RedisCache redisCache;





    /**
     * 添加预约挂号信息
     *
     * @param reservation 挂号信息
     * @return 挂号成功则返回挂号结果
     */
    @Override
    public ResultResponse<String> addRegisteredOrder(Reservation reservation) {
        //判断用户的预约时间是否有效 无效则会直接抛异常信息到前端
        validateAppointmentTime(reservation.getReservationDate(), reservation.getReservationTime());
        //组合缓存Key
        String key = cacheKey(reservation);
        try {
            //获取分布式锁 拥有锁最多6秒
            boolean lock = redisCache.lock(RedisKey.LOCK + key, 1, 6, TimeUnit.SECONDS);
            if (!lock) {//false表示已经有人抢到锁了
                return ResultResponse.error("业务繁忙，请重试");
            }
            //判断这个时间段是否已经有人预约了
            if (redisCache.hasKey(RedisKey.APPOINTMENT + key)) {
                return ResultResponse.error(ResponseCodeEnum.CONFLICT.getCode(), "该时间段已被预约!");
            }
            //从数据库查询该数据是否存在
            if (getaLong(reservation) > 0) {
                return ResultResponse.error(ResponseCodeEnum.CONFLICT.getCode(), "该时间段已预约满!");
            }
            //校验通过后设置一些必要的数据
            reservation.setStatus("1");
            reservation.setCreateBy("1");
            reservation.setCreateTime(LocalDateTime.now());
            //创建者
            reservation.setCreateBy(StpUtil.getLoginId().toString());
            // 保存到数据库
            int insert = reservationMapper.insert(reservation);
            if (insert < 0) {
                return ResultResponse.error("预约失败");
            }
            //将预约信息缓存到redis 如果该数据没有被删除或者使用则7天后清理缓存
            redisCache.setCacheObject(RedisKey.APPOINTMENT + key, reservation, 7, TimeUnit.DAYS);
            //
        } catch (Exception e) {
            throw new ServiceException("预约过程中出现错误：" + e.getMessage());
        } finally {
            //释放锁
            if (redisCache.hasKey(key)) {//对锁是否存在的判断，避免误删其他线程的锁。
                redisCache.deleteObject(key);
            }
        }
        //TODO 使用消息中间件发送短信
        return ResultResponse.success("预约成功");
    }


    /**
     * 修改预约状态
     *
     * @param status        预约状态
     * @param reservationId 取消订单ID
     * @return
     */
    @Override
    public ResultResponse<?> cancel(String status, String reservationId) {
        //查询该订单是否存在
        Reservation reservation = reservationMapper.selectById(reservationId);
        if (reservation == null) {
            return ResultResponse.error(ResponseCodeEnum.NOT_FOUND.getCode(), "未查询到相关数据");
        }
        //判断订单是否已经取消
        if (status.equals(reservation.getStatus())) {
            return ResultResponse.error("请误重复提交!");
        }
        //组合CacheKey
        String cacheKey = cacheKey(reservation);
        //修改订单信息
        reservation = new Reservation();
        reservation.setReservationId(Integer.valueOf(reservationId));
        reservation.setStatus(status);
        reservation.setUpdateTime(LocalDateTime.now());
        reservation.setUpdateBy(StpUtil.getLoginId().toString());
        int count = reservationMapper.updateById(reservation);
        if (count < 0) {
            // 抛出异常信息
            return ResultResponse.error("修改预约失败!");
        }
        // 删除修改Redis缓存
        redisCache.deleteObject(cacheKey);
        return ResultResponse.success("修改成功!");
    }

    /**
     * 更新预约信息
     *
     * @param reservation 更新数据
     * @return 更新结果
     */
    @Override
    public ResultResponse<?> modify(Reservation reservation) {
        //判断数据是否存在
        Reservation selectById = reservationMapper.selectById(reservation.getReservationId());
        if (selectById == null) {
            return ResultResponse.error("未查询到先关数据！");
        }
        //只有预约中的数据才能修改
//        if (!"1".equals(selectById.getStatus())) {
//            return ResultResponse.error(ResponseCodeEnum.NOT_PROCESSED.getCode(),
//                    ResponseCodeEnum.NOT_PROCESSED.getMessage());
//        }
        //如果用户更新了预约时间，需要判断预约时间是否合规
        if (reservation.getReservationDate() != null && reservation.getReservationTime() != null) {
            validateAppointmentTime(reservation.getReservationDate(), reservation.getReservationTime());
            //判断预约时间是否已经有人预约，如果该条数据是跟源数据是一致的那说明只是修改了其他数据，没有修改预约时间，那就不用管
            if (getaLong(reservation) > 0 && !selectById.getReservationId().equals(reservation.getReservationId())) {
                return ResultResponse.error("该时间段已被预约");
            }
        }
        //更新数据数据库数据
        reservation.setUpdateBy(StpUtil.getLoginId().toString());
        reservation.setUpdateTime(LocalDateTime.now());
        int count = reservationMapper.modifyOrder(reservation);
        if (count < 0) {
            return ResultResponse.error("修改失败!");
        }
        //更新缓存数据
        selectById = reservationMapper.selectById(reservation.getReservationId());
        String key = cacheKey(selectById);
        redisCache.setCacheObject(RedisKey.APPOINTMENT + key, selectById, 7, TimeUnit.DAYS);//有效期七天
        return ResultResponse.success();
    }

    /**
     * 查询预约状状态信息
     *
     * @param clientId 查询预约用户ID
     * @param status   预约状态
     * @return 查询结果
     */
    @Override
    public ResultResponse<List<Reservation>> queryStatus(String clientId, String status) {
        //直接查询数据库
        List<Reservation> list = reservationMapper.selectStatus(clientId, status);
        return ResultResponse.success(list);
    }


    /**
     * 查询出指定日期的可用时间
     * @param localDate 日期 yyyy-hh-dd
     * @param doctorId 医生ID
     * @return
     */
    @Override
    public ResultResponse<List<LocalTime>> availableTime(LocalDate localDate,String doctorId) {
        //查询出已经被预约时间
        List<LocalTime> localTimes = reservationMapper.notAvailableTime(localDate,doctorId);
        //计算出可预约的时间
        return ResultResponse.success(checkAvailableTime(localTimes,localDate));
    }

    /**
     * 分页查询
     * @param pageParam
     * @return
     */
    @Override
    public RequestPage<Reservation> reservationPage(ReservationListPage pageParam) {
        Page<Reservation> reservationPage = new Page<>(pageParam.getCurrent(),pageParam.getPageSize());
        LambdaQueryWrapper<Reservation> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(!pageParam.getSearchQuery().isEmpty() ,Reservation::getPhone,pageParam.getSearchQuery())
                .or()
                .like(!pageParam.getSearchQuery().isEmpty(),Reservation::getNickname,pageParam.getSearchQuery());
        queryWrapper.like(pageParam.getDoctorId() != null,Reservation::getDoctorId,pageParam.getDoctorId());
        Page<Reservation> page = reservationMapper.selectPage(reservationPage, queryWrapper);
        RequestPage<Reservation> reservationRequestPage = new RequestPage<>();
        reservationRequestPage.setData(page.getRecords());
        reservationRequestPage.setTotal((int) page.getTotal());
        return reservationRequestPage;
    }

    /**
     * 查询指定时间段内可预约的医生
     * @param appointmentTime 预约时间
     * @return
     */
    @Override
    public ResultResponse<List<Reservation>> specifyDoctor(LocalDate appointmentTime) {
        //获取当前登录医生ID
        String doctorId = StpUtil.getLoginId().toString();
        //判断日期参数是否为空
        if (appointmentTime == null) {
            //默认当前日期
            appointmentTime = LocalDate.now();
        }
        //构建查询条件
        LambdaQueryWrapper<Reservation> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Reservation::getDoctorId,doctorId)
                //状态小于3，不等于3
                .ne(Reservation::getStatus,3)
                .eq(Reservation::getReservationDate,appointmentTime);
        //查询数据
        List<Reservation> list = reservationMapper.selectList(queryWrapper);
        //返回数据
        return ResultResponse.success(list);
    }


    /**
     * 计算出可用的时间段
     * @param notAvailableTime
     * @return
     */
    private List<LocalTime> checkAvailableTime(List<LocalTime> notAvailableTime, LocalDate reservationDate) {
        // 定义当天可预约的时间段
        List<LocalTime> timeSlots = List.of(
                LocalTime.of(9, 0),
                LocalTime.of(10, 0),
                LocalTime.of(11, 0),
                LocalTime.of(13, 0),
                LocalTime.of(14, 0),
                LocalTime.of(15, 0),
                LocalTime.of(16, 0)
        );

        LocalDate today = LocalDate.now();
        LocalTime now = LocalTime.now();

        return timeSlots.stream()
                // 排除已被预约的时间段
                .filter(slot -> !notAvailableTime.contains(slot))
                // 如果预约的是今天，再排除当前时间之前的时间段
                .filter(slot -> !(reservationDate.isEqual(today) && slot.isBefore(now)))
                .toList();
    }


    /**
     * 检查预约挂号时间是否有效。
     *
     * @param appointmentTime 挂号时间
     * @return 如果预约时间有效（在当前时间 30 分钟后），返回 true；否则返回 false
     */
    private void validateAppointmentTime(LocalDate appointmentDate, LocalTime appointmentTime) {
        LocalDateTime appointmentDateTime = LocalDateTime.of(appointmentDate, appointmentTime);
        LocalDateTime now = LocalDateTime.now();

        // 校验 1：不能预约过去时间
        if (appointmentDateTime.isBefore(now)) {
            throw new ServiceException("预约时间无效：不能预约过去的时间");
        }

        // 校验 2：预约时间必须在 9:00 - 17:00
        LocalTime morningStart = LocalTime.of(9, 0);
        LocalTime eveningEnd = LocalTime.of(18, 0);
        if (appointmentTime.isBefore(morningStart) || appointmentTime.isAfter(eveningEnd)) {
            throw new ServiceException("预约时间无效：必须在早上 9 点到晚上 5 点之间");
        }

        // 校验 3：中午休息时间 12:00 - 13:00 不可预约
        LocalTime noonStart = LocalTime.of(12, 0);
        LocalTime noonEnd = LocalTime.of(13, 0);
        if (!appointmentTime.isBefore(noonStart) && appointmentTime.isBefore(noonEnd)) {
            throw new ServiceException("预约时间无效：中午 12 点到下午 1 点是休息时间");
        }

        // 校验 4：必须提前至少 30 分钟预约
        Duration duration = Duration.between(now, appointmentDateTime);
        if (duration.toMinutes() < 30) {
            throw new ServiceException("预约时间无效：必须在当前时间 30 分钟后");
        }
    }


    /**
     * 从数据库查询 某个时间段的数据是否存在
     *
     * @param reservation
     * @return
     */
    private Long getaLong(Reservation reservation) {
        //从数据库也查询该时间段是否有人已经预约
        LocalTime appointmentTime = reservation.getReservationTime();
        LocalDate appointmentDate = reservation.getReservationDate();
        String doctorId = reservation.getDoctorId().toString();

        // 计算当前小时段的起止时间
        LocalTime startOfHour = appointmentTime.truncatedTo(ChronoUnit.HOURS);          // 10:00
        LocalTime endOfHour = startOfHour.plusHours(1);                                 // 11:00

        LambdaQueryWrapper<Reservation> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Reservation::getReservationDate, appointmentDate);
        wrapper.eq(Reservation::getDoctorId, doctorId);
        wrapper.ge(Reservation::getReservationTime, startOfHour); // >= 10:00
        wrapper.lt(Reservation::getReservationTime, endOfHour);   // < 11:00

        return reservationMapper.selectCount(wrapper);

    }


    /**
     * 组合 cacheKey
     *
     * @param reservation 实体数据
     * @return redisKey
     */
    private String cacheKey(Reservation reservation) {
        return String.format("%s:%s:%s",
                reservation.getDoctorId(),//医生ID（确保每一个医生都是单独的一把锁）
                reservation.getReservationDate().getDayOfMonth(),//日
                reservation.getReservationTime().getHour());//预约时间（时）
    }
}




